namespace Core.Base
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using Contracts.Enums;
    using Contracts.Interfaces;
    using System.Collections.ObjectModel;
    using System.Globalization;

    /// <summary>
    /// Base robot module class
    /// </summary>
    public abstract class ModuleBase : PropertyChangedBase, IModule, IEquatable<ModuleBase>, IEquatable<IModule>
    {
        private readonly string _name;
        private readonly string _description;
        private readonly SizesEnum _size;
        private readonly TechnologyLevelsEnum _technologyLevel;
        private readonly IModifier[] _modifiers;
        private readonly ModuleGroupsEnum _group;
        private readonly ModuleMetaGroupsEnum _metaGroup;
        private readonly IEnumerable<LazyFeature> _lazyFeatures;

        private IEnumerable<ICondition<IExtension>> _conditions;
        private bool _isActive;
        private IAmmunition _loadedAmmunition;
        private ObservableCollection<IAmmunition> _usableAmmunitions;

        /// <summary>
        /// Create a new instance
        /// </summary>
        protected ModuleBase(){}
        /// <summary>
        /// Creates a new instance
        /// </summary>
        protected ModuleBase(string name, string description, ModuleMetaGroupsEnum metaGroup, ModuleGroupsEnum group, TechnologyLevelsEnum technologyLevel, SizesEnum size, IEnumerable<LazyFeature> features, IEnumerable<ICondition<IExtension>> conditions, IModifier[] modifiers)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            if (technologyLevel == TechnologyLevelsEnum.NotDefined)
                throw new InvalidEnumArgumentException("technologyLevel");
            if (size == SizesEnum.NotDefined)
                throw new InvalidEnumArgumentException("size");
            if (group == ModuleGroupsEnum.NotDefined)
                throw new InvalidEnumArgumentException("group");
            if (metaGroup == ModuleMetaGroupsEnum.NotDefined)
                throw new InvalidEnumArgumentException("metaGroup");

            _name = name;
            _description = description;
            _metaGroup = metaGroup;
            _group = group;
            _technologyLevel = technologyLevel;
            _size = size;
            _lazyFeatures = features ?? new LazyFeature[]{};
            _modifiers = modifiers ?? new IModifier[]{};
            _conditions = conditions;
        }
        /// <summary>
        /// Determines if conditions exist
        /// </summary>
        public bool HasConditions
        {
            get { return _conditions!=null && _conditions.Any() ; }
        }
        /// <summary>
        /// Obtains or defines the conditions
        /// </summary>
        public IEnumerable<ICondition<IExtension>> Conditions
        {
            get { return _conditions; }
            set { _conditions = value; NotifyPropertyChange(()=>Conditions);}
        }
        /// <summary>
        /// Obtains or defines the features
        /// </summary>
        public IEnumerable<IFeature<object>> Features
        {
            get { return GetFeatures(); }
        }
        /// <summary>
        /// Determines if therre is any features
        /// </summary>
        public bool HasFeatures
        {
            get { return _lazyFeatures.Any(); }
        }
        /// <summary>
        /// Obtains or defeines the modeule modifiers
        /// </summary>
        public IEnumerable<IModifier> Modifiers
        {
            get { return _modifiers; }
        }
        /// <summary>
        /// Determines if there is any modifier with IsVisible set to true.
        /// </summary>
        public bool HasVisibleModifiers
        {
            get { return Modifiers != null && Modifiers.Where(m => m.IsVisible).Any(); }
        }
        /// <summary>
        /// Obtains or defines the module name
        /// </summary>
        public string Name
        {
            get { return _name; }
            
        }
        /// <summary>
        /// Obtains or defines the module description
        /// </summary>
        public string Description
        {
            get { return _description; }
            
        }
        /// <summary>
        /// Obtains or defines the module size
        /// </summary>
        public SizesEnum Size
        {
            get { return _size; }
            
        }
        /// <summary>
        /// Obtains or defines the module technology level
        /// </summary>
        public TechnologyLevelsEnum TechnologyLevel
        {
            get { return _technologyLevel; }
           
        }
        /// <summary>
        /// Obtains or defines if the module is active
        /// </summary>
        public virtual bool IsActive
        {
            get { return _isActive; }
            set { _isActive = value; NotifyPropertyChange(()=>IsActive); }
        }
        /// <summary>
        /// Obtains or defines the module group
        /// </summary>
        public virtual ModuleGroupsEnum Group
        {
            get { return _group; }
            
        }
        /// <summary>
        /// Obtains the module meta group
        /// </summary>
        public virtual ModuleMetaGroupsEnum MetaGroup
        {
            get { return _metaGroup; }
            
        }
        /// <summary>
        /// Obtains the activation type
        /// </summary>
        public abstract ModuleActivationTypesEnum ActivationType { get; }
        /// <summary>
        /// Obtains the module slot group 
        /// </summary>
        public abstract SlotGroupsEnum SlotGroup { get; }
        /// <summary>
        /// Obtains the module slot type
        /// </summary>
        public abstract SlotTypesEnum SlotType { get; }
        /// <summary>
        /// Determines if the modules can use ammo or charges
        /// </summary>
        public bool CanHaveAmmunitions
        {
            get { return GetFeatures(f => f.FeatureType == FeaturesEnum.AmmoType).Any(); }
        }
        /// <summary>
        /// Determines if the module has ammunitions loaded 
        /// </summary>
        public bool HasAmmunitionsLoaded
        {
            get { return _loadedAmmunition != null && _loadedAmmunition != NullAmmunition.Instance; }
        }
        /// <summary>
        /// Obtains or defines the ammunition loaded
        /// </summary>
        public IAmmunition Ammunitions
        {
            get { return _loadedAmmunition; }
            set
            {
                _loadedAmmunition = value;
                NotifyPropertyChange(()=> HasAmmunitionsLoaded, () => Ammunitions );
            }
        }
        /// <summary>
        /// Obtains or defines the list of ammunitions usable by the module
        /// if the module can use ammunitions
        /// </summary>
        public ObservableCollection<IAmmunition> UsableAmmunitions
        {
            get { return _usableAmmunitions; }
            set { _usableAmmunitions = value;  }
        }
        /// <summary>
        /// Obtains the usable ammunition type
        /// </summary>
        public AmmunitionTypesEnum UsableAmmunitionType
        {
            get
            {
                return CanHaveAmmunitions
                           ? (AmmunitionTypesEnum)GetFeatures(f => f.FeatureType == FeaturesEnum.AmmoType).First().Value
                           : AmmunitionTypesEnum.NotDefined ;
            }
        }
        /// <summary>
        /// Obtains the module cpu usage
        /// </summary>
        public double? CpuUsage
        {
            get
            {
                var feature = GetFeatures(f =>
                                          f.FeatureType == FeaturesEnum.CpuUsage ||
                                          f.FeatureType == FeaturesEnum.WeaponCpuUsage ||
                                          f.FeatureType == FeaturesEnum.ArmorModuleCpuUsage ||
                                          f.FeatureType == FeaturesEnum.ShieldCpuUsage ||
                                          f.FeatureType == FeaturesEnum.ShieldHardenerCpuUsage)
                                    .FirstOrDefault();

                return feature != null 
                    ? feature.IsModified
                    ? (double)feature.ModifiedValue : (double)feature.Value : (double?)null;
            }
        }
        /// <summary>
        /// Obtains the module reactor usage
        /// </summary>
        public double? ReactorUsage
        {
            get
            {
                var feature = GetFeatures(f => 
                    f.FeatureType == FeaturesEnum.ReactorUsage ||
                    f.FeatureType == FeaturesEnum.WeaponReactorUsage ||
                    f.FeatureType == FeaturesEnum.ArmorModuleReactorUsage ||
                    f.FeatureType == FeaturesEnum.ShieldReactorUsage ||
                    f.FeatureType == FeaturesEnum.ShieldHardenerReactorUsage).FirstOrDefault();

                return feature != null ? feature.IsModified ? (double)feature.ModifiedValue : (double)feature.Value : (double?)null;
            }
        }
        /// <summary>
        /// Obtains the module optimal range
        /// </summary>
        public double? OptimalRange
        {
            get
            {
                var feature = GetFeatures(f => 
                    f.FeatureType == FeaturesEnum.OptimalRange ||
                    f.FeatureType == FeaturesEnum.MissileLauncherOptimal ||
                    f.FeatureType == FeaturesEnum.SmallMagneticWeaponOptimal ||
                    f.FeatureType == FeaturesEnum.RemoteArmorRepairOptimal ||
                    f.FeatureType == FeaturesEnum.MediumMagneticWeaponOptimal ||
                    f.FeatureType == FeaturesEnum.SmallFirearmOptimal ||
                    f.FeatureType == FeaturesEnum.MediumFirearmOptimal ||
                    f.FeatureType == FeaturesEnum.SmallLaserOptimal ||
                    f.FeatureType == FeaturesEnum.MediumLaserOptimal ||
                    f.FeatureType == FeaturesEnum.EnergyTransfererOptimal ||
                    f.FeatureType == FeaturesEnum.SignalMarkerOptimal ||
                    f.FeatureType == FeaturesEnum.EnergyNeutralizerOptimal ||
                    f.FeatureType == FeaturesEnum.EnergyDrainerOptimal ||
                    f.FeatureType == FeaturesEnum.DemobilizerOptimal ||
                    f.FeatureType == FeaturesEnum.SensorSuppressorOptimal ||
                    f.FeatureType == FeaturesEnum.RemoteSensorAmplifierOptimal ||
                    f.FeatureType == FeaturesEnum.CargoScannerOptimal ||
                    f.FeatureType == FeaturesEnum.ChassisScannerOptimal ||
                    f.FeatureType == FeaturesEnum.HarvesterOptimal ||
                    f.FeatureType == FeaturesEnum.MinerOptimal ||
                    f.FeatureType == FeaturesEnum.SapModuleOptimal ).FirstOrDefault();

                return feature != null
                    ? feature.IsModified ? (double)feature.ModifiedValue : (double)feature.Value
                    : (double?)null;
            }
        }
        /// <summary>
        /// Obtains the module falloff usage
        /// </summary>
        public double? Falloff
        {
            get
            {
                var feature = GetFeatures(f => 
                    f.FeatureType == FeaturesEnum.Falloff || 
                    f.FeatureType == FeaturesEnum.SmallMagneticWeaponFalloff ||
                    f.FeatureType == FeaturesEnum.MediumMagneticWeaponFalloff ||
                    f.FeatureType == FeaturesEnum.SmallFirearmFalloff ||
                    f.FeatureType == FeaturesEnum.MediumFirearmFalloff ||
                    f.FeatureType == FeaturesEnum.SmallLaserFalloff ||
                    f.FeatureType == FeaturesEnum.MediumLaserFalloff ||
                    f.FeatureType == FeaturesEnum.CargoScannerFalloff ||
                    f.FeatureType == FeaturesEnum.ChassisScannerFalloff ).FirstOrDefault();

                return feature != null ? feature.IsModified ? (double)feature.ModifiedValue : (double)feature.Value : (double?)null;
            }
        }
        /// <summary>
        /// Obtains the module accumulutor usage
        /// </summary>
        public double? AccumulatorUsage
        {
            get
            {
                var feature = GetFeatures(f => 
                    f.FeatureType == FeaturesEnum.AccumulatorUsage || 
                    f.FeatureType == FeaturesEnum.WeaponAccumulatorUsage || 
                    f.FeatureType == FeaturesEnum.ArmorRepairAccumulatorUsage ||
                    f.FeatureType == FeaturesEnum.RemoteArmorRepairAccumulatorUsage ||
                    f.FeatureType == FeaturesEnum.NexusAccumulatorUsage ).FirstOrDefault();

                return feature != null ? feature.IsModified ? (double)feature.ModifiedValue : (double)feature.Value : (double?)null;
            }
        }

        public double? Damage
        {
            get
            {
                var feature = GetFeatures(f =>
                    f.FeatureType == FeaturesEnum.SmallMagneticWeaponDamage ||
                    f.FeatureType == FeaturesEnum.MediumMagneticWeaponDamage ||
                    f.FeatureType == FeaturesEnum.SmallLaserDamage ||
                    f.FeatureType == FeaturesEnum.MediumLaserDamage ||
                    f.FeatureType == FeaturesEnum.SmallFirearmDamage ||
                    f.FeatureType == FeaturesEnum.MediumFirearmDamage).FirstOrDefault();

                return feature != null
                    ? feature.IsModified ? (double)feature.ModifiedValue : (double)feature.Value
                    : (double?)null;
            }
        }

        /// <summary>
        /// Obtains the module cycle time
        /// </summary>
        public double? CycleTime
        {
            get
            {
                var feature = GetFeatures(f => 
                    f.FeatureType == FeaturesEnum.CycleTime || 
                    f.FeatureType == FeaturesEnum.SmallMagneticWeaponCycleTime || 
                    f.FeatureType == FeaturesEnum.ArmorRepairCycleTime ||
                    f.FeatureType == FeaturesEnum.MissileLauncherCycleTime ||
                    f.FeatureType == FeaturesEnum.RemoteArmorRepairCycleTime ||
                    f.FeatureType == FeaturesEnum.ShieldCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumMagneticWeaponCycleTime ||
                    f.FeatureType == FeaturesEnum.SmallFirearmCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumFirearmCycleTime ||
                    f.FeatureType == FeaturesEnum.SmallLaserCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumLaserCycleTime ||
                    f.FeatureType == FeaturesEnum.SmallMissileLauncherCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumMissileLauncherCycleTime ||
                    f.FeatureType == FeaturesEnum.NexusModuleCycleTime ||
                    f.FeatureType == FeaturesEnum.SignalDetectorCycleTime ||
                    f.FeatureType == FeaturesEnum.SignalMaskerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyTransfererCycleTime ||
                    f.FeatureType == FeaturesEnum.SignalMarkerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyDrainerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyNeutralizerCycleTime ||
                    f.FeatureType == FeaturesEnum.DemobilizerCycleTime ||
                    f.FeatureType == FeaturesEnum.SensorSuppressorCycleTime ||
                    f.FeatureType == FeaturesEnum.SensorAmplifierCycleTime ||
                    f.FeatureType == FeaturesEnum.RemoteSensorAmplifierCycleTime ||
                    f.FeatureType == FeaturesEnum.CargoScannerCycleTime ||
                    f.FeatureType == FeaturesEnum.EccmCycleTime ||
                    f.FeatureType == FeaturesEnum.ChassisScannerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyInjectorCycleTime ||
                    f.FeatureType == FeaturesEnum.GeoscannerCycleTime ||
                    f.FeatureType == FeaturesEnum.HarvesterCycleTime ||
                    f.FeatureType == FeaturesEnum.MinerCycleTime ||
                    f.FeatureType == FeaturesEnum.SapModuleCycleTime ||
                    f.FeatureType == FeaturesEnum.EcmCycleTime ||
                    f.FeatureType == FeaturesEnum.TargetMarkerCycleTime).FirstOrDefault();

                return feature != null 
                    ? feature.IsModified ? (double)feature.ModifiedValue : (double)feature.Value 
                    : (double?)null;
            }
        }
        /// <summary>
        /// Obtains the accumulator used per second for active modules
        /// </summary>
        public double? AccumulatorUsagePerSecond
        {
            get
            {
                if (!IsActive) return null;

                var cycleTimeFeature = GetFeatures(f =>
                    f.FeatureType == FeaturesEnum.CycleTime ||
                    f.FeatureType == FeaturesEnum.SmallMagneticWeaponCycleTime ||
                    f.FeatureType == FeaturesEnum.ArmorRepairCycleTime ||
                    f.FeatureType == FeaturesEnum.MissileLauncherCycleTime ||
                    f.FeatureType == FeaturesEnum.RemoteArmorRepairCycleTime ||
                    f.FeatureType == FeaturesEnum.ShieldCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumMagneticWeaponCycleTime ||
                    f.FeatureType == FeaturesEnum.SmallFirearmCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumFirearmCycleTime ||
                    f.FeatureType == FeaturesEnum.SmallLaserCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumLaserCycleTime ||
                    f.FeatureType == FeaturesEnum.SmallMissileLauncherCycleTime ||
                    f.FeatureType == FeaturesEnum.MediumMissileLauncherCycleTime ||
                    f.FeatureType == FeaturesEnum.NexusModuleCycleTime ||
                    f.FeatureType == FeaturesEnum.SignalDetectorCycleTime ||
                    f.FeatureType == FeaturesEnum.SignalMaskerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyTransfererCycleTime ||
                    f.FeatureType == FeaturesEnum.SignalMarkerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyDrainerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyNeutralizerCycleTime ||
                    f.FeatureType == FeaturesEnum.DemobilizerCycleTime ||
                    f.FeatureType == FeaturesEnum.SensorSuppressorCycleTime ||
                    f.FeatureType == FeaturesEnum.SensorAmplifierCycleTime ||
                    f.FeatureType == FeaturesEnum.RemoteSensorAmplifierCycleTime ||
                    f.FeatureType == FeaturesEnum.CargoScannerCycleTime ||
                    f.FeatureType == FeaturesEnum.EccmCycleTime ||
                    f.FeatureType == FeaturesEnum.ChassisScannerCycleTime ||
                    f.FeatureType == FeaturesEnum.EnergyInjectorCycleTime ||
                    f.FeatureType == FeaturesEnum.GeoscannerCycleTime ||
                    f.FeatureType == FeaturesEnum.HarvesterCycleTime ||
                    f.FeatureType == FeaturesEnum.MinerCycleTime ||
                    f.FeatureType == FeaturesEnum.SapModuleCycleTime ||
                    f.FeatureType == FeaturesEnum.EcmCycleTime ||
                    f.FeatureType == FeaturesEnum.TargetMarkerCycleTime).FirstOrDefault();

                if (cycleTimeFeature == null) return null;

                var accUsageFeature = GetFeatures(f =>
                    f.FeatureType == FeaturesEnum.AccumulatorUsage ||
                    f.FeatureType == FeaturesEnum.WeaponAccumulatorUsage ||
                    f.FeatureType == FeaturesEnum.ArmorRepairAccumulatorUsage ||
                    f.FeatureType == FeaturesEnum.RemoteArmorRepairAccumulatorUsage ||
                    f.FeatureType == FeaturesEnum.NexusAccumulatorUsage).FirstOrDefault();

                if (accUsageFeature == null) return null;

                var accUsagePerSecond = 
                   (double)(accUsageFeature.IsModified ? accUsageFeature.ModifiedValue : accUsageFeature.Value) /
                   (double)(cycleTimeFeature.IsModified ? cycleTimeFeature.ModifiedValue : cycleTimeFeature.Value);

                return Math.Round(accUsagePerSecond,2, MidpointRounding.AwayFromZero);
            }
        }
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(ModuleBase other)
        {
            if (ReferenceEquals(null, other)) return false;
            return ReferenceEquals(this, other) || Equals(other._name, _name);
        }
        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>. </param><filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return obj.GetType() == typeof (ModuleBase) && Equals((ModuleBase) obj);
        }
        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public object Clone()
        {
             return Activator.CreateInstance(this.GetType());
        }
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(IModule other)
        {
            if (ReferenceEquals(null, other)) return false;
            return ReferenceEquals(this, other) || Equals(other.Name, _name);
        }
        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override string ToString()
        {
            return _name;
        }

        private IEnumerable<IFeature<object>> GetFeatures(Predicate<IFeature<object>> predicate = null)
        {
            using(var enu = _lazyFeatures.GetEnumerator())
                while (enu.MoveNext())
                    if (predicate == null)
                        yield return enu.Current.Feature;
                    else if (predicate(enu.Current.Feature))
                        yield return enu.Current.Feature;

        }
    }
}